Control Structures: Review and New
So far I’ve discussed five control structures in SPEED/ASM:

   the JMP instruction (unconditional goto),
   the JSR instruction (jump to subroutine),
   the SPEED/ASM IFx and IFx0 routines,
   the FOR/FOR0 loop and the 6502 branch instructions.

SPEED/ASM supports several additional control structures, many of them extremely powerful
and easy to use.

Included in this set of routines are ONX-GOTO, CASE, CASEI, INSET, and NOTINSET.

A Quick Review of SPEED/ASM Control Structures
Before describing the new control structures, a quick review of the control routines
I’ve presented thus far may help tie up any loose ends.

The JMP instruction is actually a 6502 instruction.

It is almost identical to the GOTO statement in Basic —
it transfers control to a new statement which doesn’t necessarily follow the JMP.

The syntax for the JMP instruction is:

      JMP <label>

where <label> is a valid statement label in your SPEED/ASM program.

The JSR instruction is used to call a 6502 or SPEED/ASM subroutine.

The RTS instruction is used to return from a user subroutine.

The JSR/RTS combination is used identically to the GOSUB/RETURN statements in Basic
(except, of course, you specify a statement label instead of a line number).

The syntax for the JSR statement is:

      JSR <label>

where <label> is the name of the user subroutine you wish to call.

All user subroutines should be terminated with the RTS instruction.
The RTS instruction does not allow any operands; its syntax is:

      RTS

The JSR instruction also is used to call routines in the SPEED/ASM package,
since all SPEED/ASM routines are nothing more than 6502 subroutines.

The SPEED/ASM IFx/IFx0 routines come in two flavours
(actually three, but I’ve only discussed two versions so far):

   the IFI/IFI0 routines and the IFS/IFS0 routines.

The IFI/IFI0 routines are used to compare two integer values.

IFI compares two integer variables and IFI0 compares an integer variable to an integer constant.
The IFS/IFS0 routines compare two SPEED/ASM strings.

IFS compares two string variables and IFS0 compares a string variable to a string constant.

The syntax for the IFx routines is:


      JSR IFI
      ADR <Ivar1>,<op>,<Ivar2>

   and

      JSR IFS
      ADR <Svar1>,<op>,<Svar2>

where <Ivar1> and <Ivar2> are the names of properly defined SPEED/ASM integer variables
and <Svar1> and <Svar2> are the names of SPEED/ASM string variables.

<op> is any of the SPEED/ASM logical operators:

“IFS compares two string variables and IFS0 compares a string variable to a string constant.”

      EQ
      NE
      LE
      GE
      LT
      GT

as defined in the SPEED/ASM equates file.

The IFx0 routines compare a variable to a constant;

   the syntax for these two instructions is:

      JSR IFI0
      ADR <Ivar>,<op>,<Iconst>

   and

      JSR IFS0
      ADR <Svar>,<op>
      BYT “string constant,0

where <Iconst> is an integer constant and “string constant” is a zero terminated character string.

Immediately after the call to one of the IF/IFx0 routines you should use

the 6502/LISA BTR (branch if true) or BFL (branch if false) instruction to test
the comparison for true or false.

The SPEED/ASM FOR/FOR0 instructions emulate the Basic loops of the same name.

The syntax for the FOR0 loop is:

      JSR FOR0
      ADR <Ivar>,<start>,<end>
      ..  ..
      ..  ..
      ..  ..
      JSR NEXT

where <Ivar> is the name of a SPEED/ASM integer variable and <start> and <end> are
integer constants.

This emulates Basic statements of the form:

      FOR I = 1 TO 10

The SPEED/ASM FOR loop handles the case where integer variables are required for the
starting, ending or stepsize variables.

The syntax for the FOR loop is:

      JSR FOR
      ADR <Ivar>,<vstrt>,<vend>,<vstep>
      ..
      ..
      ..
      ..
      JSR NEXT

where <Ivar>, <vstrt>, <vend>, and <vstep> all are SPEED/ASM integer variable names.

To compare two single byte values pure 6502 code is used.

The 6502 CMP instruction, along with the various branch instructions,
lets you compare a value in memory to the value in the accumulator.

After the instruction CMP<operand> where <operand> is

any of:

      <Ivar>
      #<Ivar>
      /<Ivar>
      <Ivar>,X
      <Ivar>,Y
      (ZPG,X)
      (ZPG),Y

the 6502 branch instructions can be used to determine how the variables compare.

The applicable instructions are:

      BEQ — Branch if the accumulator equals the operand of CMP.
      BNE — Branch if the acc does not equal the operand of CMP.
      BLT — Branch if the acc is less than the operand of CMP.
      BGE — Branch if the acc is greater than or equal to the operand of the CMP instruction.

The CSP Instruction
The CSP instruction (Call SPEED/ASM Procedure) is a new pseudo-opcode/6502 instruction
added to LISA v2.6 repertoire specifically for use by SPEED/ASM programmers.

CSP combines LISA’s JSR and .DA statements.

This instruction may help make writing SPEED/ASM programs much easier.

The syntax for the CSP instruction is:

      CSP <adrs> {<.da expressions>} which is identical to the statements:
      JRS <adrs>
      .DA <.da expressions>

There are four different types of <.da expressions>: a full address, a string expression,
a high order byte value, and a low order byte value.

Any time an address expression appears in the operand field of a .DA statement
(or in the <.da expression> portion of the CSP statement) two bytes of object code are generated.

If a string appears in the operand field of a .DA or CSP instruction, then a single byte
of object code is emitted for each character in the string. For our purposes you should
enclose the string with quotation marks.

If an address expression is immediately preceded by a pound sign (#), then only one byte
of object code is output; its value will be the low order byte of the specified address
expression.

If an address expression is prefaced with a slash (/), then the high order byte of the
address expression’s value is output.

With the CSP instruction you can type many SPEED/ASM statements on a single line.

Some examples of the CSP statement in operation include:

      CSP PRINT,“Printing strings with CSP”,#0
      CSP IFI,I,LE,J
      CSP IFS0,STRNG,EQ,“STRING COMPARE”,#0
      CSP FOR0,I,1,100
      CSP FOR,I,STRT,END,STEP
      CSP PRTINT,I

Due to its convenience I will use CSP in many of the examples that follow.

The ONXGOTO Subroutine
The first new SPEED/ASM control routine I will discuss is the ONXGOTO routine.

The ONXGOTO routine transfers control to a new statement depending upon the value in the
X registers.

The syntax for the ONXGOTO instruction is:

      JSR ONXGOTO
      ADR  <numentries>
      ADR <label0>,<label1>,...,<labeln>

where <numentries> is the number of labels that follow the

<numentries> value and <label1>..<labeln> are labels within your SPEED/ASM program.

If the X register contains zero, control is transferred to <label0>.

If the X register contains one, ONXGOTO will jump to location <label1>, etc.

If the X register contains a value greater than <numentries>, then program execution
continues with the 6502 statement immediately following the <labeln> entry.

Warning!

It is critical that the <numentries> value exactly represents the number of addresses
that follow.

If <numentries> is too small and the ONXGOTO routine falls through,

the 6502 will attempt to execute one of the trailing addresses as valid 6502 code.

This usually will cause the program to bomb.

One way to guarantee that the <numentries> value is always correct is to use a call
to ONXGOTO of the form:

             JSR ONXGOTO
             ADR ENTRY0/2
      TABLE0 ADR ADRS1,ADRS2,...,ADRSn
      ;
      ENTRY0 EQU *-TABLE0

The “*” operator in the operand field says “give me the current program address.”

By subtracting the address of the jump table’s first entry from the address of the first
byte after the address table, this equate calculates the number of bytes in the table.

Since we're interested in the number of entries, not the number of bytes in the table,
you must divide the ENTRY0 label by two (since there are two bytes per table entry) to
compute the proper value.

Using this method for specifying the number of entries in the ONX-GOTO routine lets you
modify the number of entries in the address table and automatically update the
<numentries> value.

If you don’t use this method, adding or deleting an entry from the address table forces
you to increment or decrement the <numentris> to make up for the change.

If you don’t, disaster may strike the next time you run the program.

Since it is so easy to forget to update the <numentris> value when modifying
the address table, using the equate to automatically calculate the number of entries in
the table is a smart thing to do.

The CASE Statement
SPEED/ASM supports a control structure very similar to the CASE statement found in
high level languages like Pascal and “C”.

Two versions of the SPEED/ASM CASE statement are provided:

CASE and CASE1.

CASE is a single byte CASE statement;
     it compares the value in the 6502 accumulator against several values and branches
     if the accumulator equals one of those values.

CASEI compares the contents of a SPEED/ASM integer variable to
      one of several integer values and branches if a match is made.

The format for the CASE statement is:

      CSP CASE,#<numentries>
      DA #<value1>,<adrs1>
      DA #<value2>,<adrs2>
      DA #<value3>,<adrs3>
      .     .        .
      .     .        .
      .     .        .
      DA <valuen> <adrsn>

where <numentries> is the number of cases present,

<valuei> (i=0..n) are the single byte values you want to compare the 6502 accumulator
against,

  and

<adrsi> (i=0..n) are labels where SPEED/ASM will jump to if a match is found.

During execution the CASE statement compares the value in the 6502 accumulator to <value0>.

If the accumulator is equal to <value0>, control is transferred to location <adrs0>.

If the accumulator does not equal <value0>, the accumulator is compared against <value1>
and control is transferred to location <adrs1> if a match is made.

If the accumulator doesn’t equal <value1> it’s compared to <value2>, etc.

If the accumulator isn’t equal to any of the values present in the CASE statement,
control is resumed at the first statement after the <valuen> entry.

SPEED/ASM will bomb horribly if <numentries> doesn’t properly reflect the number of cases
in the CASE statement.

The safest way to specify this value is to have LISA v2.6 calculate it for you.

This can be accomplished using code of the form:

      CSP   CASE,#NUMCASES/3
  CASETBL   .DA <value0>,<adrs0>
            .DA <value1>,<adrs1>
              .    .        .
              .    .        .
              .    .        .
            .DA <valuen>,<adrsn>
  ;

  NUMCASES = *-CASETBL

This code automatically computes the number of cases present in the case list.

Furthermore, you don’t have to change anything if you add or delete cases later on.

The CASEI statement is similar to the CASE statement;
    the only difference is the CASEI routine lets you compare a SPEED/ASM integer variable
    to a series of integer values (CASE only performs byte comparisons).

The syntax for the CASEI statement is:

      CSP  CASE,<numentries>,<SAvariable>
      ADR  <value0>,<adrs0>
      ADR  <value1>,<adrs1>
      ADR  <value2>,<adrs2>
        .    .        .
        .    .        .
        .    .        .
      ADR  <valuen>,<adrsn>

The variable <numentries> is the number of cases
(a two-byte value for CASEI and a single byte value for CASE);

<SAvariable> is the name of a SPEED/ASM integer variable;
<valuei> (i=0..n) are 16-bit integer values,

   and
   
<adrsi> (i=0..n) are the names of statement labels in your SPEED/ASM program where a
                 branch will be made to if <SAvariable> equals <valuei>.

Like the CASE and ONXGOTO statements, <numentries> must accurately describe the number of
entries in the case table or SPEED/ASM may hang.

To make sure you enter the proper value you should let LISA v2.6 compute the number of
entries for you using code of the form:

         CSP CASEI,NUMCASES/4,VAR
   CASES ADR <value0>,<adrs0>
         ADR <value1>,<adrsl>
         ADR <value2>,<adrs2>
          .    .        .
          .    .        .
          .    .        .
         ADR <valuen>,<adrsn>
   ;
   NUMCASES = *—CASES

NUMCASES must be divided by four, since there are four bytes in each case entry.

The INSET and NOTINSET Routines
The INSET and NOTINSET routines compare the accumulator against a set of values and branch
to a single location if the accumulator is in the specified set (INSET), or is not in the
specified set (NOTINSET).

The syntax for these two routines is identical:

         CSP INSET,<numentries>
         BYT <value0>,<value1>,...,<valuen>
         ADR <adrs>
   or
         CSP NOTINSET,<numentries>
         BYT <value0>,<value1>,...,<valuen>
         ADR  <adrs>

In the case of INSET the 6502 accumulator is compared to the values <value0>..<valuen>.

If the accumulator is equal to any value in this list, control is transferred to
location <adrs>.

If the accumulator doesn’t equal any of the values in the set, program execution continues
with the first statement after the INSET statement.

The NOTINSET routine is used to ensure that the accumulator doesn’t contain a value in a
given set.

Control is transferred to the branch address if and only if the value in the accumulator
does not match any of the values in the set.

If the accumulator matches one of the values in the set that follows the call to NOTINSET,
control is transferred to the first statement after the NOTINSET jump address,

Conclusions
The program control transfer routines provided in the SPEED/ASM package are very powerful.

This month’s demonstration program shows how these control structures can be used to set
up some very flexible menu programs.

“The program control transfer routines provided in the SPEED/ASM package are very powerful.

Along with the power, however, comes responsibility.”

Along with the power, however, comes responsibility.

It is very important that you make sure all <numentries> values properly reflect
the number of entries in the table following the SPEED/ASM routine call.

SPEED/ASM uses this information to determine how many cases to check, where the first
instruction following the case table can be found, etc. Failure to provide proper data
in this parameter slot probably will cause your program to hang.